#include "../inc/Mat.h"

Mat::Mat (void) {
	SetData (MatrixData ());
}

Mat::Mat (const MatrixData& data) {
	SetData (data);
}

Mat::Mat (const IMat& src) {
	MatrixData newData;
	unsigned int rowCount = src.GetRowCount ();
	for (unsigned int i = 0; i < rowCount; ++i) {
		newData.push_back (src.GetRow (i));
	}
	SetData (newData);
}

bool Mat::Load (const IReader* reader) {
	MatrixData data;
	bool loadResult = reader->Read (&data);
	if (loadResult) {
		SetData (data);
	}
	return (loadResult);
}

void Mat::SetData (MatrixData data) {
	currentRowCount = defaultRowCount = data.size ();
	currentColumnSize = defaultColumnSize = data.size ();
	if (defaultRowCount > 0) {
		currentColumnCount = defaultColumnCount = data.at(0).size();
		currentRowSize = defaultRowSize = data.at(0).size();
	}
	else {
		currentColumnCount = defaultColumnCount = 0;
		currentRowSize = defaultRowSize = 0;
	}
	defaultData.clear ();
	defaultData = data;
	ResetROI ();
}

bool Mat::Save (const IWriter* writer) {
	bool saveResult = writer->Write (currentData);
	return (saveResult);
}

bool Mat::SetROI (unsigned int x1, unsigned int y1, unsigned int x2, unsigned int y2) {
	bool setROIresult = false;
	
	// check coordinates of ROI
	if (x2 < currentRowSize && y2 < currentColumnSize && x1 <= x2 && y1 <= y2) {
		ROIx1 = x1;
		ROIy1 = y1;
		ROIx2 = x2;
		ROIy2 = y2;
		SetCurrentData ();
		setROIresult = true;
	}
	return (setROIresult);
}

void Mat::SetCurrentData (void) {
	matrixChange = false;
	if (defaultColumnCount > 0 && defaultRowCount > 0) {
		currentData.clear ();
		for (unsigned int i = ROIy1; i < ROIy2; ++i) {
			MatrixRow row;
			for (unsigned int j = ROIx1; j < ROIx2; ++j) {
				row.push_back (defaultData[i][j]);
			}
			currentData.push_back (row);
		}
		currentRowCount = currentData.size ();
		currentColumnSize = currentData.size ();
		if (currentRowCount > 0) {
			currentColumnCount = currentData.at(0).size();
			currentRowSize = currentData.at(0).size();
		}
		else {
			currentColumnCount = 0;
			currentRowSize = 0;
		}
	}
}

void Mat::ResetROI (void) {
	ROIx1 = 0;
	ROIy1 = 0;
	ROIx2 = defaultRowSize;
	ROIy2 = defaultColumnSize;
	SetCurrentData ();
}

void Mat::SaveChanges (void) {
	if (matrixChange) {
		for (unsigned int i = ROIy1; i <= ROIy2; ++i) {
			for (unsigned int j = ROIx1; j <= ROIx2; ++j) {
				defaultData[i][j] = currentData[i-ROIy1][j-ROIx1];
			}
		}
	}
	matrixChange = false;
}

void Mat::DiscardChanges (void) {
	SetCurrentData ();
}

MatrixColumn Mat::GetColumn (unsigned int columnNumber) const {
	MatrixColumn column;
	if (columnNumber < currentColumnCount) {
		for (unsigned int i = 0; i < currentColumnSize; ++i) {
			column.push_back (currentData[i][columnNumber]);
		}
	}
	return (column);
}

MatrixRow Mat::GetRow (unsigned int rowNumber) const {
	MatrixRow row;
	if (rowNumber < currentRowCount) {
		row = currentData.at (rowNumber);
	}
	return (row);
}

unsigned int Mat::GetRowCount (void) const {
	return (currentRowCount);
}

unsigned int Mat::GetColumnCount (void) const {
	return (currentColumnCount);
}

unsigned int Mat::GetColumnSize (void) const {
	return (currentColumnSize);
}

unsigned int Mat::GetRowSize (void) const {
	return (currentRowSize);
}

bool Mat::matWaveletByColumn (const std::vector <MatrixValue>& highFilter, const std::vector <MatrixValue>& lowFilter) {
	bool waveletResult = true;
	unsigned int columnSize = GetColumnCount ();
	for (unsigned int i = 0; i < columnSize; ++i) {
		waveletResult = waveletResult * columnWavelet (i, highFilter, lowFilter) != 0;
	}
	if (!waveletResult) {
		DiscardChanges ();
	}
	return (waveletResult);
}

bool Mat::matWaveletByRow (const std::vector <MatrixValue>& highFilter, const std::vector <MatrixValue>& lowFilter) {
	// see for more info - http://sergeshibaev.ru/index.php/programmer-notes/10-fwtsharc
	bool waveletResult = true;
	unsigned int rowCount = GetRowCount ();
	for (unsigned int i = 0; i < rowCount; ++i) {
		bool res = rowWavelet (i, highFilter, lowFilter);
		//waveletResult = waveletResult * rowWavelet (i, highFilter, lowFilter) != 0;
	}
	if (!waveletResult) {
		DiscardChanges ();
	}
	return (waveletResult);
}

bool Mat::columnWavelet (const unsigned int& columnNumber, const std::vector <MatrixValue>& highFilter, const std::vector <MatrixValue>& lowFilter) {
	bool waveletResult = false;
	MatrixColumn src = GetColumn (columnNumber);
	unsigned int filterSize = lowFilter.size();
	unsigned int srcSize = src.size ();
	if (filterSize == highFilter.size () && filterSize > 0 && filterSize < srcSize) {
		for (unsigned int i = 0; i < filterSize; ++i) {
			src.push_back(src[i]);
		}
		srcSize = srcSize/2;
		for (unsigned int i = 0; i < srcSize; ++i) {
			currentData [i][columnNumber] = src[i*2] * highFilter[0];
			currentData [i+srcSize][columnNumber] = src[i*2] * lowFilter[0];
			for (unsigned int j = 1; j < filterSize; ++j) {
				currentData [i][columnNumber] += src[i*2+j] * highFilter[j];
				currentData [i+srcSize][columnNumber] += src[i*2+j] * lowFilter[j];
			}
		}
		waveletResult = true;
		this->matrixChange = true;
	}
	return (waveletResult);
}

bool Mat::rowWavelet (const unsigned int& rowNumber, const std::vector <MatrixValue>& highFilter, const std::vector <MatrixValue>& lowFilter) {
	bool waveletResult = false;
	MatrixRow src = GetRow (rowNumber);
	unsigned int filterSize = lowFilter.size();
	unsigned int srcSize = src.size ();
	if (filterSize == highFilter.size () && filterSize > 0 && filterSize <= srcSize) {
		for (unsigned int i = 0; i < filterSize; ++i) {
			src.push_back(src[i]);
		}
		srcSize = srcSize/2;
		for (unsigned int i = 0; i < srcSize; ++i) {
			currentData[rowNumber][i] = src[i*2] * highFilter[0];
			currentData[rowNumber][i+srcSize] = src[i*2] * lowFilter[0];
			for (unsigned int j = 1; j < filterSize; ++j) {
				currentData[rowNumber][i] += src[i*2+j] * highFilter[j];
				currentData[rowNumber][i+srcSize] += src[i*2+j] * lowFilter[j];
			}
		}
		waveletResult = true;
		this->matrixChange = true;
	}
	return (waveletResult);
}